﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using theeg.GraphWpf.Arrangement;
using theeg.GraphWpf.Models;

namespace UnitTests.Graph
{
    [TestClass]
    public class LayeredArrangementTest
    {
        private IGraph BuildComplexGraph()
        {
            var graphDummy = new GraphDummy
            {
                Nodes = new ObservableCollection<INode>(),
                Transitions = new ObservableCollection<Transition>()
            };
            var node = new Node { Text = "" };
            var nodeD = new Node { Text = "DependencyAnalysis" };
            var nodeDi = new Node { Text = "DependencyAnalysis.Internal" };
            var nodeDs = new Node { Text = "DependencyAnalysis.Serialization" };
            var nodeDd = new Node { Text = "DependencyAnalysis.DataModel" };
            var nodeDas = new Node { Text = "DependencyAnalysis.Algorithms.SubstringDetection" };
            var nodeDac = new Node { Text = "DependencyAnalysis.Algorithms.CycleDetection" };
            graphDummy.Nodes.Add(node);
            graphDummy.Nodes.Add(nodeD);
            graphDummy.Nodes.Add(nodeDi);
            graphDummy.Nodes.Add(nodeDs);
            graphDummy.Nodes.Add(nodeDd);
            graphDummy.Nodes.Add(nodeDas);
            graphDummy.Nodes.Add(nodeDac);
            var transition1 = new Transition();
            var transition2 = new Transition();
            var transition3 = new Transition();
            var transition4 = new Transition();
            var transition5 = new Transition();
            var transition6 = new Transition();
            var transition7 = new Transition();
            var transition8 = new Transition();
            var transition9 = new Transition();
            var transition10 = new Transition();
            var transition11 = new Transition();
            var transition12 = new Transition();
            graphDummy.Transitions.Add(transition1);
            graphDummy.Transitions.Add(transition2);
            graphDummy.Transitions.Add(transition3);
            graphDummy.Transitions.Add(transition4);
            graphDummy.Transitions.Add(transition5);
            graphDummy.Transitions.Add(transition6);
            graphDummy.Transitions.Add(transition7);
            graphDummy.Transitions.Add(transition8);
            graphDummy.Transitions.Add(transition9);
            graphDummy.Transitions.Add(transition10);
            graphDummy.Transitions.Add(transition11);
            graphDummy.Transitions.Add(transition12);
            transition1.SetConnection(nodeD, nodeDs);
            transition2.SetConnection(nodeDi, nodeDd);
            transition3.SetConnection(nodeDs, nodeDd);
            transition4.SetConnection(nodeD, nodeDd);
            transition5.SetConnection(nodeD, nodeDi);
            transition6.SetConnection(nodeDi, nodeDas);
            transition7.SetConnection(nodeDs, nodeD);
            transition8.SetConnection(nodeDd, nodeD);
            transition9.SetConnection(nodeDi, nodeD);
            transition10.SetConnection(nodeDd, node);
            transition11.SetConnection(nodeDd, node);
            transition12.SetConnection(nodeD, nodeDac);
            return graphDummy;
        }

        private IGraph BuildCircleGraph()
        {
            var graphDummy = new GraphDummy
            {
                Nodes = new ObservableCollection<INode>(),
                Transitions = new ObservableCollection<Transition>()
            };
            var node1 = new Node { Text = "1" };
            var node2 = new Node { Text = "2" };
            var node3 = new Node { Text = "3" };
            var node4 = new Node { Text = "4" };
            graphDummy.Nodes.Add(node1);
            graphDummy.Nodes.Add(node2);
            graphDummy.Nodes.Add(node3);
            graphDummy.Nodes.Add(node4);
            var transition1 = new Transition();
            var transition2 = new Transition();
            var transition3 = new Transition();
            var transition4 = new Transition();
            graphDummy.Transitions.Add(transition1);
            graphDummy.Transitions.Add(transition2);
            graphDummy.Transitions.Add(transition3);
            graphDummy.Transitions.Add(transition4);
            transition1.SetConnection(node1, node2);
            transition2.SetConnection(node2, node3);
            transition3.SetConnection(node3, node4);
            transition4.SetConnection(node4, node1);
            return graphDummy;
        }

        private IGraph BuildBackwardGraph()
        {
            var graphDummy = new GraphDummy
            {
                Nodes = new ObservableCollection<INode>(),
                Transitions = new ObservableCollection<Transition>()
            };
            var node1 = new Node();
            var node2 = new Node();
            var node3 = new Node();
            var node4 = new Node();
            graphDummy.Nodes.Add(node1);
            graphDummy.Nodes.Add(node2);
            graphDummy.Nodes.Add(node3);
            graphDummy.Nodes.Add(node4);
            var transition1 = new Transition();
            var transition2 = new Transition();
            var transition3 = new Transition();
            var transition4 = new Transition();
            graphDummy.Transitions.Add(transition1);
            graphDummy.Transitions.Add(transition2);
            graphDummy.Transitions.Add(transition3);
            graphDummy.Transitions.Add(transition4);
            transition1.SetConnection(node1, node2);
            transition2.SetConnection(node2, node3);
            transition3.SetConnection(node2, node4);
            transition4.SetConnection(node2, node1);
            return graphDummy;
        }

        [TestMethod]
        public void WhenBackwardComplexThenRemoveOneTransition()
        {
            var graph = BuildComplexGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();

            var nodeD = graph.Nodes[1];
            var nodeDs = graph.Nodes[3];
            var removedTransitions = arrangement.GetRemovedTransitions();
            Assert.IsTrue(removedTransitions.Any(x => x.Source == nodeDs && x.Target == nodeD));
        }

        [TestMethod]
        public void WhenCircleThenInvertFirstEncounteredTransition()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            var transition = graph.Transitions[3];
            Assert.AreEqual(graph.Nodes[0], transition.Source);
            Assert.AreEqual(graph.Nodes[3], transition.Target);
        }

        [TestMethod]
        public void WhenCircleThenInvertOneTransition()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();

            Assert.AreEqual(1, arrangement.GetInvertedTransitions().Count);
        }

        [TestMethod]
        public void WhenGraphIsDirectedAcyclicThenInvertNothing()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.RemoveCycles1();

            Assert.AreEqual(1, arrangement.GetInvertedTransitions().Count);
        }

        [TestMethod]
        public void WhenTransitionWasInvertedThenRestoreOriginalSituation()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.RestoreCycles1();
            var transition = graph.Transitions[3];
            Assert.AreEqual(graph.Nodes[3], transition.Source);
            Assert.AreEqual(graph.Nodes[0], transition.Target);
        }

        [TestMethod]
        public void WhenTransitionInBothDirectionsThenRemoveSecond()
        {
            var graph = BuildBackwardGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            var transition = graph.Transitions[3];
            var node1 = graph.Nodes[1];
            var node2 = graph.Nodes[0];
            Assert.IsFalse(node1.OutgoingEdges.Contains(transition));
            Assert.IsFalse(node2.IncomingEdges.Contains(transition));
        }

        [TestMethod]
        public void WhenTransitionInBothDirectionsThenRemoveOne()
        {
            var graph = BuildBackwardGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();

            Assert.AreEqual(1, arrangement.GetRemovedTransitions().Count);
        }

        [TestMethod]
        public void WhenGraphIsDirectedAcyclicThenRemoveNothing()
        {
            var graph = BuildBackwardGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.RemoveCycles1();

            Assert.AreEqual(1, arrangement.GetRemovedTransitions().Count);
        }

        [TestMethod]
        public void WhenTransitionWasRemovedThenRestoreOriginalSituation()
        {
            var graph = BuildBackwardGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.RestoreCycles1();
            var transition = graph.Transitions[3];
            var node = graph.Nodes[1];
            Assert.IsTrue(node.OutgoingEdges.Contains(transition));
        }

        [TestMethod]
        public void DummyNodesWhenTargetIsOneLevelBelowSourceThenIgnore()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 1 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 2 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 1 });

            arrangement.IntroduceDummyNodes1();

            Assert.AreEqual(0, arrangement.GetDummyNodes().Count);
        }

        [TestMethod]
        public void DummyNodesWhenTargetIsOneLevelAboveSourceThenIgnore()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 1 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 1 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 2 });

            arrangement.IntroduceDummyNodes1();

            Assert.AreEqual(0, arrangement.GetDummyNodes().Count);
        }

        [TestMethod]
        public void DummyNodesWhenTargetIsThreeLevelBelowSourceThenAddTwoDumies()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 3 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 2 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 1 });
            arrangement.CreateDummyLayers(3);

            arrangement.IntroduceDummyNodes1();

            Assert.AreEqual(2, arrangement.GetDummyNodes().Count);
        }

        [TestMethod]
        public void DummyNodesWhenTargetIsThreeLevelAboveSourceThenAddTwoDumies()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 3 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 1 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 2 });
            arrangement.CreateDummyLayers(3);

            arrangement.IntroduceDummyNodes1();

            Assert.AreEqual(2, arrangement.GetDummyNodes().Count);
        }

        [TestMethod]
        public void DummyNodeWhenCreatedThenLastDummyIsConnectedToOriginalTarget()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 3 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 2 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 1 });
            arrangement.CreateDummyLayers(3);

            arrangement.IntroduceDummyNodes1();

            var dummyNodes = arrangement.GetDummyNodes();
            Assert.AreEqual(graph.Nodes[1], dummyNodes[1].OutgoingEdges[0].Target);
        }

        [TestMethod]
        public void DummyNodeWhenCreatedThenFirstDummyIsConnectedToOriginalSource()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.SetNodeArrangement(graph.Nodes[0], new NodeArrangementData { Y = 0 });
            arrangement.SetNodeArrangement(graph.Nodes[1], new NodeArrangementData { Y = 3 });
            arrangement.SetNodeArrangement(graph.Nodes[2], new NodeArrangementData { Y = 2 });
            arrangement.SetNodeArrangement(graph.Nodes[3], new NodeArrangementData { Y = 1 });
            arrangement.CreateDummyLayers(3);

            arrangement.IntroduceDummyNodes1();

            var dummyNodes = arrangement.GetDummyNodes();
            Assert.AreEqual(graph.Nodes[0], dummyNodes[0].IncomingEdges[0].Source);
        }

        [TestMethod]
        public void WhenLineThenAllLayersHaveOneNode()
        {
            var graph = BuildCircleGraph();

            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.AssignLayers1();

            var arrangementData = arrangement.GetNodeArrangementData();
            Assert.AreEqual(0, arrangementData[graph.Nodes[0]].Y);
            Assert.AreEqual(1, arrangementData[graph.Nodes[1]].Y);
            Assert.AreEqual(2, arrangementData[graph.Nodes[2]].Y);
            Assert.AreEqual(3, arrangementData[graph.Nodes[3]].Y);
        }

        [TestMethod]
        public void WhenYFormThenLastLayerHasTwoNodes()
        {
            var graph = BuildBackwardGraph();

            var arrangement = new LayeredArrangementStub(graph, false);
            arrangement.RemoveCycles1();
            arrangement.AssignLayers1();

            var arrangementData = arrangement.GetNodeArrangementData();
            Assert.AreEqual(0, arrangementData[graph.Nodes[0]].Y);
            Assert.AreEqual(1, arrangementData[graph.Nodes[1]].Y);
            Assert.AreEqual(2, arrangementData[graph.Nodes[2]].Y);
            Assert.AreEqual(2, arrangementData[graph.Nodes[3]].Y);
        }

        [Ignore]
        [TestMethod]
        public void DummiesToEdges()
        {
        }

        [Ignore]
        [TestMethod]
        public void ReduceCrossings()
        {
        }

        [TestMethod]
        public void WhenFlushPositionsVerticalThenSumHeightWithMarginY()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            var node = graph.Nodes[0];
            node.Width = 100;
            node.Height = 50;
            arrangement.SetNodeArrangement(node, new NodeArrangementData { X = 1, Y = 1 });

            arrangement.FlushPositions1();

            Assert.AreEqual(300, node.CenterX);
            Assert.AreEqual(140, node.CenterY);
        }

        [TestMethod]
        public void WhenFlushPositionsHorizontalThenSumHeightWithMarginX()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, true);
            var node = graph.Nodes[0];
            node.Width = 100;
            node.Height = 50;
            arrangement.SetNodeArrangement(node, new NodeArrangementData { X = 1, Y = 1 });

            arrangement.FlushPositions1();

            Assert.AreEqual(165, node.CenterX);
            Assert.AreEqual(275, node.CenterY);
        }

        [TestMethod]
        public void CompleteArrangementVerticalOnValidGraphThrowsNoException()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, false);
            Assert1.DoesNotThrow<Exception>(arrangement.Arrange);
        }

        [TestMethod]
        public void CompleteArrangementHorizontalOnValidGraphThrowsNoException()
        {
            var graph = BuildCircleGraph();
            var arrangement = new LayeredArrangementStub(graph, true);
            Assert1.DoesNotThrow<Exception>(arrangement.Arrange);
        }

        public class GraphDummy : IGraph
        {
            public IList<INode> Nodes { get; set; }

            public IList<Transition> Transitions { get; set; }

            public IArrangement Arrangement { get; set; }

            public bool IsExpanded { get { return true; } }

            public void Arrange()
            {
                throw new NotImplementedException();
            }
        }

        public class LayeredArrangementStub : Layered
        {
            public LayeredArrangementStub(IGraph graph, bool horizontal)
                : base(graph, 100, 10, horizontal)
            {
                BuildArrangementData();
            }

            public void RemoveCycles1()
            {
                RemoveCycles();
            }

            public void RestoreCycles1()
            {
                RestoreCycles();
            }

            public void CreateDummyLayers(int numberOfLayers)
            {
                Layers = new List<INode>[numberOfLayers];
                for (int i = 0; i < numberOfLayers; i++)
                {
                    Layers[i] = new List<INode>();
                }
            }

            public void IntroduceDummyNodes1()
            {
                IntroduceDummyNodes();
            }

            public void SetNodeArrangement(INode node, NodeArrangementData arrangement)
            {
                ArrangementData[node] = arrangement;
            }

            public IList<INode> GetDummyNodes()
            {
                return DummyNodes.ToList();
            }

            public IList<Transition> GetInvertedTransitions()
            {
                return InvertedTransitions;
            }

            public IList<Transition> GetRemovedTransitions()
            {
                return RemovedTransitions;
            }

            public void AssignLayers1()
            {
                AssignLayersLongestPath();
            }

            public IDictionary<INode, NodeArrangementData> GetNodeArrangementData()
            {
                return ArrangementData;
            }

            public void FlushPositions1()
            {
                FlushPositions();
            }
        }
    }
}
